/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.iec.edp.caf.commons.utils;

import java.io.*;
import java.nio.charset.Charset;
import java.util.Enumeration;
import java.util.zip.*;

/**
 * 数据的压缩/解压类
 */
public class ZipHelper {

    /**
     * 压缩方法
     * @param input 待压缩内容
     * @param compressionLevel 压缩级别
     * @return 压缩后的byte数组
     */
    public static byte[] zipCompress(byte[] input,int compressionLevel){
        if(input==null||input.length==0){
            return new byte[]{};
        }

        byte[] result;
        ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();
        DeflaterOutputStream deflaterOutputStream=new DeflaterOutputStream(byteArrayOutputStream,new Deflater(compressionLevel));
        try{
            deflaterOutputStream.write(input,0,input.length);
            deflaterOutputStream.finish();
            result=byteArrayOutputStream.toByteArray();
        }catch (IOException e){
            throw new RuntimeException("数据流操作失败");
        }finally {
            if(deflaterOutputStream!=null){
                try{
                    deflaterOutputStream.close();
                }catch (IOException e){
                    throw new RuntimeException("数据流关闭失败");
                }
            }
            if(byteArrayOutputStream!=null){
                try{
                    byteArrayOutputStream.close();
                }catch (IOException e){
                    throw new RuntimeException("数据流关闭失败");
                }
            }
        }
        return result;
    }

    /**
     *压缩方法
     * @param srcStream 待压缩内容
     * @param zipStream 压缩后的流
     * @param compressionLevel 压缩级别
     * @param closeSrcStream 是否关闭srcStream
     */
    public static void zipCompress(InputStream srcStream, OutputStream zipStream, int compressionLevel, boolean closeSrcStream){
        if(srcStream==null||zipStream==null){
            return;
        }
        int bufferSize=4096;  //缓冲区的尺寸，一般是2048的倍数
        byte[] buffer=new byte[bufferSize]; //创建缓冲数据
        DeflaterOutputStream deflaterOutputStream=new DeflaterOutputStream(zipStream,new Deflater(compressionLevel));
        try{
            while ((bufferSize=srcStream.read(buffer))>0){
                deflaterOutputStream.write(buffer,0,bufferSize);
            }
        }catch (IOException e){
            throw new RuntimeException("数据流操作失败");
        }finally {
            if(srcStream!=null){
                try{
                    if(closeSrcStream){
                        srcStream.close();
                    }
                }catch (IOException e){
                    throw new RuntimeException("数据流关闭失败");
                }
            }
            if(deflaterOutputStream!=null){
                try{
                    deflaterOutputStream.close();
                }catch (IOException e){
                    throw new RuntimeException("数据流关闭失败");
                }
            }
        }
    }

    /**
     * 解压缩流
     * @param zipStream 压缩后的流
     * @param outputStream 解压后的流
     * @param zipStream 压缩后是否关闭输入流
     */
    public static void zipDeCompress(InputStream zipStream,OutputStream outputStream,boolean closeZipStream){
        if(zipStream==null||outputStream==null){
            return;
        }
        int bufferSize = 4096;//缓冲区的尺寸，一般是2048的倍数
        InflaterInputStream inflaterInputStream=new InflaterInputStream(zipStream);
        int size=bufferSize;
        byte[] data=new byte[bufferSize];
        try{
            while (true){
                size=inflaterInputStream.read(data,0,size);
                if(size>0){
                    outputStream.write(data,0,size);
                }else{
                    break;
                }
            }
        }catch (IOException e){
            throw new RuntimeException("数据流操作失败");
        }finally {
            if(inflaterInputStream!=null){
                try{
                    inflaterInputStream.close();
                }catch (IOException e){
                    throw new RuntimeException("数据流关闭失败");
                }
            }
            try {
                if (closeZipStream) {
                    zipStream.close();
                }
            } catch (IOException e) {
                throw new RuntimeException("数据流关闭失败");
            }
        }
    }

    /**
     * 解压缩byte数组
     * @param  input 压缩的byte数组
     * @return 解压后的byte数组
     */
    public static byte[] zipDeCompress(byte[] input){
        if(input==null||input.length==0){
            return new byte[]{};
        }
        ByteArrayInputStream byteArrayInputStream=new ByteArrayInputStream(input);
        ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();
        byte[] output=null;

        try{
            zipDeCompress(byteArrayInputStream,byteArrayOutputStream,true);
            output=byteArrayOutputStream.toByteArray();
        }finally {
            if(byteArrayInputStream!=null){
                try{
                    byteArrayInputStream.close();
                }catch (IOException e){
                    throw new RuntimeException("数据流关闭失败");
                }
            }
            if(byteArrayOutputStream!=null){
                try{
                    byteArrayOutputStream.close();
                }catch (IOException e){
                    throw new RuntimeException("数据流关闭失败");
                }
            }
        }
        return output;
    }

    /**
     *  Gzip压缩方法
     * @param srcStream 待压缩流
     * @param zipStream 压缩后的流
     * @param closeSrcStream 关闭源流
     */
    public static void gzipCompress(InputStream srcStream,OutputStream zipStream, boolean closeSrcStream){
        if(srcStream==null||zipStream==null){
            return;
        }
        int buffersize = 4096;//缓冲区的尺寸，一般是2048的倍数
        byte[] fileData = new byte[buffersize];//创建缓冲数据
        GZIPOutputStream gzipOutputStream=null;
        try{
            gzipOutputStream=new GZIPOutputStream(zipStream);
            while((buffersize=srcStream.read(fileData,0,buffersize))>0){
                gzipOutputStream.write(fileData,0,buffersize);
            }
        }catch (IOException e){
            throw new RuntimeException("数据流操作失败");
        }finally {
            if(gzipOutputStream!=null){
                try{
                    gzipOutputStream.close();
                }catch (IOException e){
                    throw new RuntimeException("数据流关闭失败");
                }
            }
            try {
                if (closeSrcStream) {
                    srcStream.close();
                }
            } catch (IOException e) {
                throw new RuntimeException("数据流关闭失败");
            }
        }
    }

    /**
     * gzip解压缩方法
     * @param zipStream 需要解压缩的流
     * @param outputStream 解压后的流
     * @param closeZipStream 解压后是否关闭传入的压缩流
     */
    public static void gzipDeCompress(InputStream zipStream,OutputStream outputStream,boolean closeZipStream) {
        if (zipStream == null || outputStream == null) {
            return;
        }
        int buffersize = 4096;//缓冲区的尺寸，一般是2048的倍数
        byte[] fileData = new byte[buffersize];//创建缓冲数据
        GZIPInputStream gzipInputStream = null;
        try {
            gzipInputStream = new GZIPInputStream(zipStream);
            while ((buffersize=gzipInputStream.read(fileData,0,buffersize))>0) {
                outputStream.write(fileData, 0, buffersize);
            }
        } catch (IOException e) {
            throw new RuntimeException("数据流操作失败");
        } finally {
            if (zipStream != null) {
                try {
                    if (closeZipStream) {
                        zipStream.close();
                    }
                } catch (IOException e) {
                    throw new RuntimeException("数据流关闭失败");
                }
            }
            if (gzipInputStream != null) {
                try {
                    gzipInputStream.close();
                } catch (IOException e) {
                    throw new RuntimeException("数据流关闭失败");
                }
            }
        }
    }


    /**
     * 压缩单个文件
     * @param fileToZip 要压缩的文件
     * @param zipedFile  压缩后的文件
     * @param compressionLevel  压缩级别
     */
    public static  void zipFile(String fileToZip,String zipedFile,int compressionLevel){
        if(fileToZip==null||"".equals(fileToZip)||zipedFile==null||"".equals(zipedFile)){
            return;
        }
        File inputFile=new File(fileToZip);
        if(!inputFile.exists()){
            throw new RuntimeException(String.format("文件%s不存在",inputFile.getAbsoluteFile()));
        }
        File zipFile=new File(zipedFile);
        if(zipFile.exists()){
            boolean result = zipFile.delete();
            if (!result){
                throw new RuntimeException(String.format("文件%s删除失败", zipFile.getAbsoluteFile()));
            }
        }
        ZipOutputStream zipOutputStream=null;
        try{
            zipOutputStream=new ZipOutputStream(new FileOutputStream(zipedFile));
            writeZip(inputFile,"",zipOutputStream,compressionLevel);
        }catch (FileNotFoundException e){
            throw new RuntimeException("文件不存在");
        }finally {
            if(zipOutputStream!=null){
                try{
                    zipOutputStream.close();
                }catch (IOException e){
                    throw new RuntimeException("数据流关闭失败");
                }
            }
        }
    }

    /**
     * 压缩多层目录
     * @param sourceDirectoryName 待压缩的全路径名称
     * @param zipedFile 压缩后的文件，需要全路径
     * @param compressionLevel 压缩级别
     */
    public static void zipFileDirectory(String sourceDirectoryName,String zipedFile,int compressionLevel){
        if(sourceDirectoryName==null||"".equals(sourceDirectoryName)||zipedFile==null||"".equals(zipedFile)){
            return;
        }
        File directory=new File(sourceDirectoryName);
        if(!directory.exists()&&!directory.isDirectory()){
            throw new RuntimeException(String.format("文件%s不存在",directory.getAbsoluteFile()));
        }
        File zipFile=new File(zipedFile);
        if(zipFile.exists()){
            throw new RuntimeException(String.format("文件%s已存在",zipFile.getAbsoluteFile()));
        }
        ZipOutputStream zipOutputStream=null;
        try{
            zipOutputStream=new ZipOutputStream(new FileOutputStream(zipedFile));
            writeZip(directory,"",zipOutputStream,compressionLevel);
        }catch (FileNotFoundException e){
            throw new RuntimeException("文件不存在");
        }finally {
            if(zipOutputStream!=null){
                try{
                    zipOutputStream.close();
                }catch (IOException e){
                    throw new RuntimeException("数据流关闭失败");
                }
            }
        }
    }

    /**
     * 压缩
     * @param file 待压缩文件
     * @param parentPath 父路径
     * @param zos  zip输出流
     * @param compressionLevel 压缩级别
     */
    private static void writeZip(File file, String parentPath, ZipOutputStream zos,int compressionLevel) {
//        ||"".equals(parentPath)
        if(file==null||parentPath==null||zos==null){
            return;
        }
        zos.setLevel(compressionLevel);
        if(file.isDirectory()){    //处理文件夹
            parentPath+=file.getName()+File.separator;
            File [] files=file.listFiles();
            if(files.length != 0)
            {
                for(File f:files){
                    writeZip(f, parentPath, zos,compressionLevel);
                }
            }
            else
            {    //空目录则创建当前目录
                try {
                    zos.putNextEntry(new ZipEntry(parentPath));
                } catch (IOException e) {
                    throw new RuntimeException("数据流操作失败");
                }
            }
        }else{
            FileInputStream fis=null;
            try {
                fis=new FileInputStream(file);
                ZipEntry ze = new ZipEntry(parentPath + file.getName());//创建压缩文件
                zos.putNextEntry(ze);//添加压缩文件
                int buffersize = 4096;//缓冲区的尺寸，一般是2048的倍数
                byte[] fileData = new byte[buffersize];//创建缓冲数据

                while((buffersize=fis.read(fileData,0,buffersize))>0){
                    zos.write(fileData,0,buffersize);
                }
            } catch (IOException e) {
                throw new RuntimeException("数据流操作失败");
            }finally{
                try {
                    if(fis!=null){
                        fis.close();
                    }
                }catch(IOException e){
                    throw new RuntimeException("数据流关闭失败");
                }
            }
        }
    }

    /**
     * 解压缩一个zip文件
     * @param zipedFile 压缩文件，需要全路径
     * @param unZipDirectoryPath 解压文件所在路径，需要全路径
     */
    public static void unZip(String zipedFile,String unZipDirectoryPath) {
        if(zipedFile==null||"".equals(zipedFile)){
            return;
        }
        File zip=new File(zipedFile);
        if (unZipDirectoryPath == null || "".equals(unZipDirectoryPath)) {
            unZipDirectoryPath=zip.getAbsolutePath().substring(0,zip.getAbsolutePath().lastIndexOf("."));
        }
        File outputDir= checkDirectoryExist(unZipDirectoryPath);

        FileOutputStream out = null;
        InputStream in = null;
        //读出文件数据
        ZipFile zipFileData = null;
        ZipFile zipFile = null;
        try {
            //指定编码
            zipFile = new ZipFile(zipedFile, Charset.forName("utf8"));
            Enumeration<? extends ZipEntry> entries = zipFile.entries();
            //处理创建文件夹
            while (entries.hasMoreElements()) {
                ZipEntry entry = entries.nextElement();
                String filePath=outputDir.getPath() + File.separator + entry.getName();
                File file = new File(filePath);
                File parentFile = file.getParentFile();
                if (!parentFile.exists()) {
                    boolean result = parentFile.mkdirs();
                    if (!result) {
                        throw new RuntimeException(String.format("文件夹%s创建失败", parentFile.getAbsoluteFile()));
                    }
                }
                if (parentFile.isDirectory()) {
                    continue;
                }
            }
            //解压缩
            zipFileData = new ZipFile(zip.getPath(), Charset.forName("utf8"));
            Enumeration<? extends ZipEntry> entriesData = zipFileData.entries();
            while (entriesData.hasMoreElements()) {
                ZipEntry entry = entriesData.nextElement();
                in = zipFile.getInputStream(entry);
                String filePath = outputDir.getPath() + File.separator + entry.getName();
                File file = new File(filePath);
                if (file.isDirectory()) {
                    continue;
                }
                out = new FileOutputStream(filePath);
                int buffersize = 4096;//缓冲区的尺寸，一般是2048的倍数
                byte[] fileData = new byte[buffersize];//创建缓冲数据
                while((buffersize=in.read(fileData,0,buffersize))>0){
                    out.write(fileData,0,buffersize);
                }
                out.flush();
                //文件夹解压时需要将所有文件流都关闭
                try{
                    if (out != null) {
                        out.close();
                    }

                    if (in != null) {
                        in.close();
                    }
                }catch (IOException e) {
                    throw new RuntimeException("数据流关闭失败");
                }
            }
        } catch (IOException e) {
            throw new RuntimeException("数据流操作失败");
        } finally {
            //这里输入流输出流关闭可以去除，为确保完全关闭，暂时保留
            try {
                if (out != null) {
                    out.close();
                }

                if (in != null) {
                    in.close();
                }
            } catch (IOException e) {
                throw new RuntimeException("数据流关闭失败");
            }
            try {
                if (zipFile != null) {
                    zipFile.close();
                }

                if (zipFileData != null) {
                    zipFileData.close();
                }
            } catch (IOException e) {
                throw new RuntimeException("文件关闭失败");
            }

        }
    }

    /**
     * 判断文件目录是否存在，不存在则创建，若存在则抛异常
     * @param dirPath 目录路径
     * @return 文件目录
     */
    private static File checkDirectoryExist(String dirPath){
        File file=new File(dirPath);
        if(file.exists()){
            throw new RuntimeException(String.format("文件%s已存在",file.getAbsoluteFile()));
        }
        if(!file.exists()){
            boolean result = file.mkdirs();
            if (!result) {
                throw new RuntimeException(String.format("文件夹%s创建失败", file.getAbsoluteFile()));
            }
        }
        return file;
    }
}
